﻿package {
	/**
	* A string formatter as described at http://en.wikipedia.org/wiki/Printf
	*/
	public class StringFormatter {
		public static function init():void {
			if(!String.prototype.hasOwnProperty("format")) String.prototype.format = format;
		}
		public static function format(fmt:String, ...params):String {
			const token:RegExp = /%(\d+$)?([0+\-#]*)(\d+)?(\.\d+)?([hh|h|l|ll|L|z|j|t])?([diufFeEgGxXoscpn%])/;
			var out:String = fmt;
			var match:Array = null;
			var repl:String = "";
			var paramIdx:int = 0;
			var num:int;
			var flt:Number;
			while ((match = token.exec(out)) != null) {
				//trace(match);
				var param:Object = params[(parseInt(match[1], 10) - 1) || paramIdx];
				var flags:String = match[2] || "";
				var padding:String = (flags.indexOf('0') > -1) ? '0' : ' ';
				var isSignShown:Boolean = (flags.indexOf('+') > -1);
				var isLeftAligned:Boolean = (flags.indexOf('-') > -1);
				var isAltForm:Boolean = (flags.indexOf('#') > -1);
				var width:int = parseInt(match[3], 10);
				var precision:int = -1;
				if (match[4]) precision = Math.max(0, Math.min(20, parseInt(match[4].slice(1), 10)));
				var n:int;
				var f:Number;
				var prefix:String;
				//trace(match[6], paramIdx, param, typeof param);
				var type:int = match[6].charCodeAt(0);
				switch(type) {
					case 0x64: // 'd'
					case 0x69: // 'i'
						n = param as int;
						if (isNaN(n)) {
							repl = "NaN";
						} else {
							repl = (n >= 0 ? n : -n).toString(10);
							while (repl.length < width) {
								if (isLeftAligned) repl += ' ';
								else repl = padding + repl;
							}
							if (n >= 0) {
								if (isSignShown) repl = '+' + repl;
							} else {
								repl = '-' + repl;
							}
						}
						break;
					case 0x75: // 'u'
						n = param as uint;
						if (isNaN(n)) {
							repl = "NaN";
						} else {
							repl = n.toString(10);
							while (repl.length < width) {
								if (isLeftAligned) repl += ' ';
								else repl = padding + repl;
							}
							if (isSignShown) repl = '+' + repl;
						}
						break;
					case 0x65: // 'e'
					case 0x45: // 'E'
						f = param as Number;
						if (isNaN(f)) {
							repl = "NaN";
						} else {
							if (precision < 0) repl = (f >= 0 ? f : -f).toString();
							else repl = (f >= 0 ? f : -f).toExponential(precision);
							if (precision == 0) repl += ".";
							while (repl.length < width) {
								if (isLeftAligned) repl += padding;
								else repl = padding + repl;
							}
							if (f >= 0) {
								if(isSignShown) repl = '+' + repl;
							} else {
								repl = '-' + repl;
							}
						}
						break;
					case 0x66: // 'f'
					case 0x46: // 'F'
					case 0x67: // 'g'
					case 0x47: // 'G'
						f = param as Number;
						if (isNaN(f)) {
							repl = "NaN";
						} else {
							if (precision < 0) repl = (f >= 0 ? f : -f).toString();
							else repl = (f >= 0 ? f : -f).toFixed(precision);
							if (precision == 0) repl += ".";
							while (repl.length < width) {
								if (isLeftAligned) repl += padding;
								else repl = padding + repl;
							}
							if (f >= 0) {
								if (isSignShown) repl = '+' + repl;
							} else {
								repl = '-' + repl;
							}
						}
						break;
					case 0x78: // 'x'
					case 0x58: // 'X'
						n = param as int;
						if (isNaN(n)) {
							repl = "NaN";
						} else {
							repl = (n >= 0 ? n : -n).toString(16);
							while (repl.length < width) {
								if (isLeftAligned) repl += ' ';
								else repl = padding + repl;
							}
							if (isAltForm && n != 0) repl = '0x' + repl;
							if (type == 0x58) repl = repl.toUpperCase();
							if (n >= 0) {
								if (isSignShown) repl = '+' + repl;
							} else {
								repl = '-' + repl;
							}
						}
						break;
					case 0x6f: // 'o'
						n = param as int;
						if (isNaN(n)) {
							repl = "NaN";
						} else {
							repl = (n >= 0 ? n : -n).toString(8);
							while (repl.length < width) {
								if (isLeftAligned) repl += ' ';
								else repl = padding + repl;
							}
							if (isAltForm && n != 0) repl = '0' + repl;
							if (n >= 0) {
								if (isSignShown) repl = '+' + repl;
							} else {
								repl = '-' + repl;
							}
						}
						break;
					case 0x73: // 's'
					case 0x63: // 'c'
						repl = (param as String) || "";
						while (repl.length < width) {
							if (isLeftAligned) repl += ' ';
							else repl = ' ' + repl;
						}
						if (precision < repl.length) repl = repl.slice(0, precision);
						break;
					case 0x25: // '%'
						repl = '%';
						break;
					case 0x70: // 'p'
					case 0x6e: // 'n'
					default:
						repl = "";
				}
				out = out.replace(token, repl);
				if(!(type == 0x25 || type == 0x70 || type == 0x6e)) ++paramIdx;
			}
			return out;
		}
	}
}
